package com.kinyx.framework.db.daos;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.List;
import java.util.function.Function;

import com.kinyx.framework.db.entities.I_ID;
import com.kinyx.framework.db.entities.I_INSERT_TIME;
import com.kinyx.framework.db.entities.I_UPDATE_TIME;
import com.kinyx.framework.db.entities.I_USING_STATUS;
import com.kinyx.framework.db.sentence.str.INSERT;
import com.kinyx.framework.db.sentence.str.SQL;
import com.kinyx.framework.db.sentence.str.SQLBuilder;
import com.kinyx.framework.db.sentence.str.UPDATE;
import com.kinyx.framework.db.vo.TableInfo;
import com.kinyx.framework.enums.impl.ErrorCodeX;
import com.kinyx.framework.enums.impl.UsingStatus;
import com.kinyx.framework.exception.KRuntimeException;
import com.kinyx.framework.reflect.KFieldUtils;
import com.kinyx.framework.utils.KDateTimeUtils;
import com.kinyx.framework.utils.KStringUtils;
import com.kinyx.framework.vo.InPageData;
import com.kinyx.framework.vo.OutPageData;

class JDBCDaoExecutor {

	private static Connection getConnection(final DaoParam dp) {
		if (dp.isDebugEnabled()) { dp.traceMessage("getConnection"); }
		final Connection connection = JDBCTool.getConnection(dp.datasource.getJdbcUrl(), dp.datasource.getUsername(), dp.datasource.getPassword());
		dp.setConnection(connection);
		return connection;
	}

	private static void closeConnection(final DaoParam dp, final Connection connection) {
		if (dp.isDebugEnabled()) { dp.traceMessage("closeConnection"); }
		JDBCTool.closeConnection(connection);
	}

	private static PreparedStatement prepareStatement(final DaoParam dp, final Connection connection, final String sql, final List<Object> params) {
		if (dp.isTraceEnabled()) { dp.traceMessage("prepareStatement"); }
		return JDBCTool.prepareStatement(connection, sql, params, () -> {
			// 回滚事务
			rollback(dp, connection);
		}, e -> {
			// 记录异常日志
			dp.logAfterExecution(e);
		});
	}

	private static void newTransaction(final DaoParam dp, final Connection connection) {
		if (dp.isDebugEnabled()) { dp.traceMessage("newTransaction"); }
		JDBCTool.newTransaction(connection, null, e -> {
			// 记录异常日志
			dp.logAfterExecution(e);
		});
	}

	private static void commitTransaction(final DaoParam dp, final Connection connection) { // 提交事务
		if (dp.isDebugEnabled()) { dp.traceMessage("commitTransaction"); }
		JDBCTool.commitTransaction(connection, () -> {
			// 回滚事务
			rollback(dp, connection);
		}, e -> {
			// 记录异常日志
			dp.logAfterExecution(e);
		});
	}

	private static void rollback(final DaoParam dp, final Connection connection) {
		if (dp.isTransaction() && (connection != null)) {
			if (dp.isWarnEnabled()) { dp.warn("rollback"); }
			JDBCTool.rollback(connection);
		}
	}

	private static Object execute(final DaoParam dp, final Function<Connection, Object> handler) {
		// 记录开始日志
		dp.logBeforeExecution();
		// 建立连接
		final Connection connection = !dp.isMain() ? dp.getConnection() : getConnection(dp);
		// 开启事务
		if (dp.isMain() && dp.isTransaction()) { newTransaction(dp, connection); }
		// 执行语句
		final Object result = handler.apply(connection);
		// 提交事务
		if (dp.isMain() && dp.isTransaction()) { commitTransaction(dp, connection); }
		// 关闭连接
		if (dp.isMain()) { closeConnection(dp, connection); }
		// 记录结束日志
		dp.logAfterExecution(null);
		// 返回结果
		return result;
	}

	static int executeUpdate(final DaoParam dp) {
		return (int) execute(dp, connection -> {
			switch (dp.type) {
				case insert:
				case update:
				case delete: {
					// 创建语句对象
					final PreparedStatement ps = prepareStatement(dp, connection, dp.sql, dp.params);
					// 执行语句
					if (dp.isDebugEnabled()) { dp.traceMessage("executeUpdate"); }
					return JDBCTool.executeUpdate(connection, ps, () -> {
						// 回滚事务
						rollback(dp, connection);
					}, e -> {
						// 记录异常日志
						dp.logAfterExecution(e);
					});

				}
				case batchInsert:
				case batchUpdate:
				case batchDelete: {
					int result = 0;
					// 批处理执行更新
					for (final List<Object> params : dp.listParams) {
						// 执行更新
						result += executeUpdate(dp.subParam(dp.type, dp.sql, params));
					}
					return result;
				}
				default :
					throw new KRuntimeException(ErrorCodeX.NotImplemented);
			}
		});
	}

	@SuppressWarnings("unchecked")
	static <T> Object executeQuery(final DaoParam dp, final Class<T> clazz, final int maxCount, final InPageData pageData) {
		return execute(dp, connection -> {
			switch (dp.type) {
				case queryValue:
				case queryList:
				case queryOne: {
					// 创建语句对象
					final PreparedStatement ps = prepareStatement(dp, connection, dp.sql, dp.params);
					// 设置查询结果最大值
					if (maxCount > 0) {
						JDBCTool.setMaxRows(connection, ps, maxCount, () -> {
							// 回滚事务
							rollback(dp, connection);
						}, e -> {
							// 记录异常日志
							dp.logAfterExecution(e);
						});
					}
					// 执行查询
					if (dp.isDebugEnabled()) { dp.traceMessage("executeQuery"); }
					final ResultSet rs = JDBCTool.executeQuery(connection, ps, () -> {
						// 回滚事务
						rollback(dp, connection);
					}, e -> {
						// 记录异常日志
						dp.logAfterExecution(e);
					});
					// 结果集转换
					switch (dp.type) {
						case queryValue:
							return JDBCTool.toValue(connection, clazz, ps, rs, () -> {
								// 回滚事务
								rollback(dp, connection);
							}, e -> {
								// 记录异常日志
								dp.logAfterExecution(e);
							});
						case queryList:
							return JDBCTool.toList(connection, clazz, ps, rs, () -> {
								// 回滚事务
								rollback(dp, connection);
							}, e -> {
								// 记录异常日志
								dp.logAfterExecution(e);
							}, dp.configs);
						case queryOne:
							return JDBCTool.toOne(connection, clazz, ps, rs, () -> {
								// 回滚事务
								rollback(dp, connection);
							}, e -> {
								// 记录异常日志
								dp.logAfterExecution(e);
							}, dp.configs);
						default :
							throw new KRuntimeException(ErrorCodeX.NeverHappens);
					}

				}
				case queryPage: {
					// 分页语句
					List<T> list;
					{
						// 构建分页查询语句
						final SQL pageSQLBean = dp.configs.getDialect().createPageSQL(dp.sql, dp.params, pageData.getPageNumber(), pageData.getPageSize());
						// 执行查询
						list = (List<T>) executeQuery(dp.subParam(DMLType.queryList, pageSQLBean.getSentence(), pageSQLBean.getParams()), clazz, 0, null);
					}
					// 统计语句
					Long total;
					{
						// 构建COUNT查询语句
						final SQL countSQLBean = dp.configs.getDialect().createCountSQL(dp.sql, dp.params);
						// 执行查询
						total = (Long) executeQuery(dp.subParam(DMLType.queryValue, countSQLBean.getSentence(), countSQLBean.getParams()), Long.class, 0, null);
					}
					// 结果集转换
					return new OutPageData<>(list, total, pageData);
				}
				default :
					throw new KRuntimeException(ErrorCodeX.NotImplemented);
			}
		});
	}

	@SuppressWarnings("deprecation")
	static void save(final JDBCDao dao, final DaoParam dp, final I_ID entity) {
		// 记录开始日志
		dp.logBeforeExecution();
		// 建立连接
		final Connection connection = getConnection(dp);
		// 开启事务
		newTransaction(dp, connection);
		final Class<? extends I_ID> clazz = entity.getClass();
		final TableInfo tableInfo = dp.configs.getTableInfo(clazz);
		final String tableName = tableInfo.getTableName();// 取表名

		if (KStringUtils.isBlank(entity.getId())) {// 1、主键无值。新增数据。
			if (dp.isTraceEnabled()) { dp.traceMessage("primary key has no value."); }
			entity.setId(dp.configs.getPKGenerator().id(32));
			// GOTO INSERT
		} else {// 2、主键有值。按照主键查表。
			if (dp.isTraceEnabled()) { dp.traceMessage("primary key has value. query exist data by primary key."); }
			final I_ID exist = dao.getOneById(dp.subParam(null, null, null), clazz, entity.getId());
			if (exist == null) {// 2.1、无数据。新增数据。
				if (dp.isTraceEnabled()) { dp.traceMessage("no exist data found."); }
				// GOTO INSERT
			} else {// 2.2、有数据。校验是否有字段修改。
				if (dp.isTraceEnabled()) { dp.traceMessage("found exist data. check modified fields."); }
				// SET语句处理
				final UPDATE update = SQLBuilder.update(tableName);
				tableInfo.getColumnInfos().forEach(columnInfo -> {
					final String fieldName = columnInfo.getFieldName();
					if (!Boolean.TRUE.equals(columnInfo.getUpdatable())) { // 不可修改的字段，跳过。
						if (dp.isTraceEnabled()) { dp.traceMessage("field[" + fieldName + "] updatable=false, skip."); }
						return;
					}
					// 先判断字段值是否一致
					final Field field = columnInfo.getField();
					final Object value$entity = KFieldUtils.get(entity, field);
					final Object value$exist = KFieldUtils.get(exist, field);
					boolean consistent;
					if (value$entity == null) {
						consistent = value$exist == null;
					} else {
						consistent = value$exist == null ? false : value$entity.equals(value$exist);
					}

					if (consistent) {
						if (dp.isTraceEnabled()) { dp.traceMessage("field[" + fieldName + "] have not been modified, ignore."); }
						return;
					} else {// 不一致则进行语句拼接
						if (dp.isTraceEnabled()) { dp.traceMessage("field[" + fieldName + "] have been modified from[" + value$exist + "] to[" + value$entity + "]."); }
						update.set(columnInfo.getColumnName(), value$entity);
					}
				});

				if (update.isEmpty()) {// 2.2.1、无字段修改。忽略。
					// 未执行更新语句，输出日志。
					if (dp.isTraceEnabled()) { dp.traceMessage("all updatable fields have not been modified, not actually executed change to database."); }
				} else {// 2.2.2、有字段修改。更新数据。
					if (dp.isTraceEnabled()) { dp.traceMessage("some fields have been modified, update data."); }
					if (dp.isInfoEnabled()) { dp.info("data before update -> " + exist.data(tableInfo)); } // 变动前数据记录在日志中。
					update.where(new SQL().x("ID = ?", entity.getId()));
					dao.update(dp.subParam(null, null, null), update);// 执行更新
				}

				// 提交事务
				commitTransaction(dp, connection);
				// 关闭连接
				closeConnection(dp, connection);
				// 记录结束日志
				dp.logAfterExecution(null);

				// 结束
				return;
			}
		}

		//INSERT
		{
			if (dp.isTraceEnabled()) { dp.traceMessage("insert new data."); }
			final INSERT insert = SQLBuilder.insert(tableName);
			tableInfo.getColumnInfos().forEach(columnInfo -> {
				final Field field = columnInfo.getField();
				final String fieldName = field.getName();
				if (!Boolean.TRUE.equals(columnInfo.getInsertable())) {
					if (dp.isTraceEnabled()) { // 不可新增的字段，跳过。
						dp.traceMessage("field[" + fieldName + "] insertable=false, skip.");
					}
					return;
				}
				insert.values(columnInfo.getColumnName(), defaultValue4Insert(entity, fieldName, KFieldUtils.get(entity, field)));
			});
			dao.insert(dp.subParam(null, null, null), insert);
			// 提交事务
			commitTransaction(dp, connection);
			// 关闭连接
			closeConnection(dp, connection);
			// 记录结束日志
			dp.logAfterExecution(null);
		}
	}

	private static Object defaultValue4Insert(final I_ID entity, final String fieldName, final Object fieldValue) {
		if (fieldValue == null) {// 没有指定的值，设置默认值。
			switch (fieldName) {
				case "usingStatus": {
					if (entity instanceof I_USING_STATUS) {
						((I_USING_STATUS) entity).setUsingStatus(UsingStatus.STAND_BY);
						return ((I_USING_STATUS) entity).getUsingStatus();
					}
				}
					break;
				case "insertTime": {
					// 新增数据，如果既是I_INSERT_TIME实现类，又是I_UPDATE_TIME实现类，则两个值应保持一致。两个字段先后顺序不一定，因而都需要实现。
					if (entity instanceof I_INSERT_TIME) {
						if (entity instanceof I_UPDATE_TIME) {
							final String updateTime = ((I_UPDATE_TIME) entity).getUpdateTime();
							if (updateTime != null) {
								((I_INSERT_TIME) entity).setInsertTime(updateTime);
								return updateTime;
							}
							final String now = KDateTimeUtils.now("yyyyMMddHHmmssSSS");
							((I_INSERT_TIME) entity).setInsertTime(now);
							((I_UPDATE_TIME) entity).setUpdateTime(now);
							return now;
						} else {
							final String now = KDateTimeUtils.now("yyyyMMddHHmmssSSS");
							((I_INSERT_TIME) entity).setInsertTime(now);
							return now;
						}
					}
				}
					break;
				case "updateTime": {
					// 新增数据，如果既是I_INSERT_TIME实现类，又是I_UPDATE_TIME实现类，则两个值应保持一致。两个字段先后顺序不一定，因而都需要实现。
					if (entity instanceof I_UPDATE_TIME) {
						if (entity instanceof I_INSERT_TIME) {
							final String insertTime = ((I_INSERT_TIME) entity).getInsertTime();
							if (insertTime != null) {
								((I_UPDATE_TIME) entity).setUpdateTime(insertTime);
								return insertTime;
							}
							final String now = KDateTimeUtils.now("yyyyMMddHHmmssSSS");
							((I_INSERT_TIME) entity).setInsertTime(now);
							((I_UPDATE_TIME) entity).setUpdateTime(now);
							return now;
						} else {
							final String now = KDateTimeUtils.now("yyyyMMddHHmmssSSS");
							((I_UPDATE_TIME) entity).setUpdateTime(now);
							return now;
						}
					}
				}
					break;
				default :
					break;
			}
		}
		return fieldValue;
	}

	@SuppressWarnings("deprecation")
	static void remove(final JDBCDao dao, final DaoParam dp, final I_ID entity) {
		// 记录开始日志
		dp.logBeforeExecution();
		final Class<? extends I_ID> clazz = entity.getClass();
		final TableInfo tableInfo = dp.configs.getTableInfo(clazz);
		final String tableName = tableInfo.getTableName();// 取表名

		if (KStringUtils.isBlank(entity.getId())) {// 1、主键无值。忽略。
			// 未执行更新语句，输出日志。
			if (dp.isTraceEnabled()) { dp.traceMessage("primary key has no value, not actually executed delete to database."); }
		} else {// 2、主键有值。按照主键查表。
			// 建立连接
			final Connection connection = getConnection(dp);
			// 开启事务
			newTransaction(dp, connection);
			if (dp.isTraceEnabled()) { dp.traceMessage("primary key has value. query exist data by primary key."); }
			final I_ID exist = dao.getOneById(dp.subParam(null, null, null), clazz, entity.getId());
			if (exist == null) {// 2.1、无数据。忽略。
				if (dp.isTraceEnabled()) { dp.traceMessage("no exist data found. not actually executed delete to database."); }
			} else {// 2.2、有数据。删除数据。
				if (dp.isTraceEnabled()) { dp.traceMessage("found exist data. delete data."); }
				if (dp.isInfoEnabled()) { dp.info("data will be deleted -> " + exist.data(tableInfo)); } // 变动前数据记录在日志中。
				dao.delete(dp.subParam(null, null, null), SQLBuilder.delete(tableName).where(new SQL().x("ID = ?", entity.getId())));
			}
			// 提交事务
			commitTransaction(dp, connection);
			// 关闭连接
			closeConnection(dp, connection);
		}
		// 记录结束日志
		dp.logAfterExecution(null);
	}

}
